package no.netlight.gotogate;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.ListIterator;

/**
 * AvinorParser gets XML data from www.avinor.no with flight information for
 * norwegian airports.</br> It uses QDParser to separate the XML feed into
 * element tags and text fields.</br> It is possible to search for a specific
 * flight by using <code>getSearchedFlight</code> which returns the searched
 * flight as a <code>Flight</code> object. </br> All flight data are saved to a
 * <code>Flight</code> object.</br> All airport data are saved to an
 * <code>Airport</code> object.</br> All airline data are saved to an
 * <code>Airline</code> object.</br>
 * 
 * @author Mats Ormehaug
 * @version %I%, %G%
 * @see QDParser
 * @see DocHandler
 * @see Flight
 * @see Airport
 * @see Airline
 */

public class AvinorParser implements DocHandler {

	/**
	 * Number of hours back in time the search for flight is valid for. This
	 * doesn't work at the Avinor API at the moment
	 */
	private String hoursFrom = "1";
	/** Number of hours forward in time the search for flight is valid for */
	private String hoursTo;

	private String flightId;
	private String direction;
	private String date;
	private ArrayList<Flight> allFlightList;
	private ArrayList<Flight> searchedFlightList;
	private ArrayList<Airport> airportList;
	private ArrayList<Airline> airlineList;
	private String currentElement;
	private String currentKey;
	private String currentVal;
	private Flight flight;
	private Airport yourAirport;
	private Airport otherAirport;
	private Airport tempAirport;
	private Airline airline;

	/**
	 * Class constructor for real flight data
	 * 
	 * @param flightId
	 *            A String of up to 6 characters, example "SK4296".
	 * @param airportCode
	 *            A String of 3 characters defining the airport by IATA code,
	 *            example "OSL" for Oslo Gardermoen Airport.
	 * @param direction
	 *            One character, either "D" for departure, or "A" for arrival.
	 * @param date
	 *            A String defining the traveldate, on the form "YYYY-MM-DD",
	 *            example "2010-04-19"
	 * @param hours
	 *            A String defining the number of hours from now the search for
	 *            flights should apply
	 */
	public AvinorParser(String flightId, String airportCode, String direction,
			String date, String hours) {
		yourAirport = new Airport(airportCode);
		flight = new Flight();
		this.flightId = flightId;
		this.date = date;
		hoursTo = hours;
		this.direction = direction;
		flightScheduleURL(true);
	}

	/**
	 * Class constructor for sample test flight data
	 * 
	 * @param flightId
	 *            A String of up to 6 characters, example "SK4296".
	 * @param airportCode
	 *            A String of 3 characters defining the airport by IATA code,
	 *            example "OSL" for Oslo Gardermoen Airport.
	 * @param direction
	 *            One character, either "D" for departure, or "A" for arrival.
	 * @param date
	 *            A String defining the traveldate, on the form "YYYY-MM-DD",
	 *            example "2010-04-19"
	 * @param test
	 *            True for real flight data. False for sample test data.
	 */
	public AvinorParser(String flightId, String airportCode, String direction,
			String date, boolean realData) {
		yourAirport = new Airport(airportCode);
		flight = new Flight();
		this.flightId = flightId;
		this.date = date;
		hoursTo = "24";
		this.direction = direction;
		flightScheduleURL(realData);
	}

	/**
	 * Generates a URL query for the entire flight schedule from a given
	 * airport, sends it to Avinor.no, and runs the QDParser on the URL. Uses
	 * the airport object and direction parameter given by the class constructor
	 * 
	 * @param real
	 *            True for real flight data. False for sample test data.
	 */
	private void flightScheduleURL(boolean realData) {
		// the mockFlightScheduleString should contain departure flights from
		// OSL for a 24 hour time period
		String mockFlightScheduleString = "http://dl.dropbox.com/u/2941214/gotogate/avinordata/XmlFeed.asp.htm";

		String realFlightScheduleString = "http://flydata.avinor.no/XmlFeed.asp?"
				+ "TimeFrom="
				+ hoursFrom
				+ "&TimeTo="
				+ hoursTo
				+ "&airport="
				+ yourAirport.getCode() + "&direction=" + direction;

		String flightScheduleString = "";
		if (realData)
			flightScheduleString = realFlightScheduleString;
		else
			flightScheduleString = mockFlightScheduleString;

		try {
			URL flightSchedule = new URL(flightScheduleString);
			BufferedReader read = new BufferedReader(new InputStreamReader(
					flightSchedule.openStream()));
			QDParser.parse(this, read);
			read.close();
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Sets status name on a given flight based on status code.</br> Generates a
	 * URL based on the statuscode for a flight, sends it to Avinor.no, and runs
	 * QDParser on the URL to set the equivalent status name in english and
	 * norwegian. </br> Status code with equivalent status name: </br>
	 * <ul>
	 * <li>A = "Arrived" (norwegian:"Landet") </br>
	 * <li>C = "Cancelled" (norwegian:"Innstilt") </br>
	 * <li>D = "Departured" (norwegian:"Avreist") </br>
	 * <li>E = "New time" (norwegian:"Ny tid") </br>
	 * <li>N = "New info" (norwegian:"Ny info")
	 * </ul>
	 * 
	 * @see QDParser
	 * @see Flight
	 */
	private void statusCodeURL(String status) {
		String statusCodeString = "http://flydata.avinor.no/flightStatuses.asp?"
				+ "code=" + status;

		try {
			URL statusCode = new URL(statusCodeString);
			BufferedReader read = new BufferedReader(new InputStreamReader(
					statusCode.openStream()));
			QDParser.parse(this, read);
			read.close();
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Gets the name of the airport by sending the IATA airport code to
	 * www.avinor.no.</br> If used with null as parameter it returns all airport
	 * listed at Avinor.
	 * 
	 * @param airport
	 *            The airport that needs a name. If null, all airports.
	 */
	public void airportNameURL(Airport airport) {
		String airportNameString;
		tempAirport = new Airport();
		tempAirport = airport;
		if (airport != null) {
			airportNameString = "http://flydata.avinor.no/airportNames.asp?"
					+ "airport=" + airport.getCode();
		} else
			airportNameString = "http://flydata.avinor.no/airportNames.asp?";

		try {
			URL airportName = new URL(airportNameString);
			BufferedReader read = new BufferedReader(new InputStreamReader(
					airportName.openStream()));
			QDParser.parse(this, read);
			read.close();
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Gets a list of names of all airlines listed at Avinor
	 */
	public void airlinesURL() {
		String airlinesString = "http://flydata.avinor.no/airlineNames.asp?";

		try {
			URL airlines = new URL(airlinesString);
			BufferedReader read = new BufferedReader(new InputStreamReader(
					airlines.openStream()));
			QDParser.parse(this, read);
			read.close();
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	// end of URL methods

	public void startDocument() {
		allFlightList = new ArrayList<Flight>();
		searchedFlightList = new ArrayList<Flight>();
		// System.out.println("start document");
	}

	public void endDocument() {
		// System.out.println("  end document");
	}

	public void startElement(String tag, Hashtable h) {
		currentElement = tag;

		// System.out.println("    start elem: " + tag);
		Enumeration<?> e = h.keys();
		while (e.hasMoreElements()) {
			String key = (String) e.nextElement();
			String val = (String) h.get(key);
			// System.out.println("      " + key + " = " + val);

			currentKey = key;
			currentVal = val;

			// status & time
			if (currentElement.equals("status")) {
				if (currentKey.equals("code")) {
					flight.setStatusCode(currentVal);
				} else if (currentKey.equals("time")) {
					flight.setStatusDate(getDateString(currentVal));
					flight.setStatusTime(getTimeString(currentVal));
				}

				// flight status & text
			} else if (currentElement.equals("flightStatus")) {
				if (currentKey.equals("code"))
					flight.setStatusCode(currentVal);
				else if (currentKey.equals("statusTextEn"))
					flight.setStatusTextEnglish(currentVal);
				else if (currentKey.equals("statusTextNo"))
					flight.setStatusTextNorwegian(currentVal);

				// airport code & name
			} else if (currentElement.equals("airportName")) {
				if (currentKey.equals("code"))
					tempAirport.setCode(currentVal);
				else if (currentKey.equals("name"))
					tempAirport.setName(currentVal);

				// airline code & name
			} else if (currentElement.equals("airlineName")) {
				if (currentKey.equals("code"))
					airline.setCode(currentVal);
				else if (currentKey.equals("name"))
					airline.setName(currentVal);
			}
		}// while
	}

	public void endElement(String tag) {
		currentElement = "end";
		// System.out.println("    end elem: " + tag);
	}

	/**
	 * Sets the data in each element text to corresponding fields in the flight
	 * object
	 */
	public void text(String str) {
		if (!currentElement.equals("end")) {

				// flight
			if (currentElement.equals("flight")) {
				flight = new Flight();
				allFlightList.add(flight);
				if (currentKey.equals("uniqueID"))
					flight.setUniqueId(currentVal);
			}

				// airline
			else if (currentElement.equals("airline")) {
				airline = new Airline(str);
				flight.setAirline(airline);

				// flight_id
			} else if (currentElement.equals("flight_id")) {
				flight.setFlightId(str);
				if (flightId != null && flightId.equals(str)) {
					searchedFlightList.add(flight);
				}

				// dom_int
			} else if (currentElement.equals("dom_int")) {
				String domInt = new String();
				if (str.equals("I"))
					domInt = "International";
				else if (str.equals("S"))
					domInt = "Schengen";
				else if (str.equals("D"))
					domInt = "Domestic";
				flight.setDomesticOrInternational(domInt);

				// schedule_time
			} else if (currentElement.equals("schedule_time")) {
				flight.setScheduleDate(getDateString(str));
				flight.setScheduleTime(getTimeString(str));

				// arr_dep
			} else if (currentElement.equals("arr_dep")) {
				flight.setArriveOrDeparture(str);

				// airport
			} else if (currentElement.equals("airport")) {
				otherAirport = new Airport(str);
				if (direction.equals("A")) {
					flight.setDepartureAirport(otherAirport);
					flight.setDestinationAirport(yourAirport);
				} else if (direction.equals("D")) {
					flight.setDepartureAirport(yourAirport);
					flight.setDestinationAirport(otherAirport);
				}

				// via_airport
			} else if (currentElement.equals("via_airport")) {
				airportList = new ArrayList<Airport>();
				if (str.length() > 3) {
					String[] split = str.split(",");
					for (int i = 0; i < split.length; i++) {
						otherAirport = new Airport(split[i]);
						airportList.add(otherAirport);
						flight.setViaAirport(airportList);
					}
				} else {
					otherAirport = new Airport(str);
					airportList.add(otherAirport);
					flight.setViaAirport(airportList);
				}

				// check_in
			} else if (currentElement.equals("check_in")) {
				flight.setCheckInDesk(str);

				// gate
			} else if (currentElement.equals("gate")) {
				flight.setGate(str);

				// belt_number
			} else if (currentElement.equals("belt_number")) {
				flight.setBeltNumber(str);
			}

		}

		// System.out.println("        text: " + str);
	}

	/**
	 * Sets a substring with only the date from the original date string from
	 * Avinor, that includes date and time.
	 * 
	 * @param str
	 *            Original date from Avinor
	 * @return The date
	 */
	private String getDateString(String str) {
		String date = str.substring(0, 10);
		return date;
	}

	/**
	 * Sets a substring with only the time from the original date string from
	 * Avinor, that includes date and time.
	 * 
	 * @param str
	 *            Original date from Avinor
	 * @return The time
	 */
	private String getTimeString(String str) {
		String time = str.substring(11);
		return time;
	}
	

	public Flight getSearchedFlight() {

		ListIterator<Flight> listIterator = searchedFlightList.listIterator();
		Flight searchedFlight = null;

		while (listIterator.hasNext()) {
			Flight currentFlight = (Flight) listIterator.next();
			if (date.equals(currentFlight.getScheduleDate())
					&& flightId.equals(currentFlight.getFlightId())) {
				statusCodeURL(currentFlight.getStatusCode());
				airportNameURL(currentFlight.getDestinationAirport());
				airportNameURL(currentFlight.getDepartureAirport());
				searchedFlight = currentFlight;
			}
		}

		return searchedFlight;
	}

}// AvinorParser

